package com.fowo.api.sys.component;

import com.fowo.api.common.model.RException;
import com.fowo.api.sys.annotation.Idempotent;
import com.fowo.api.sys.annotation.IdempotentParamKey;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.util.StringUtils;

import java.lang.reflect.Method;
import java.lang.reflect.Parameter;

/**
 * 幂等切面实现
 * @author yl_ls
 */
@Aspect
@Configuration
@Order(2)
@Slf4j
public class IdempotentAop {
    private final RedissonClient redissonClient;

    public IdempotentAop(RedissonClient redissonClient) {
        this.redissonClient = redissonClient;
    }

    @Around("execution(public * * (..)) && @annotation(com.fowo.api.sys.annotation.Idempotent)")
    public Object interceptor(ProceedingJoinPoint joinPoint) {
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        Method method = methodSignature.getMethod();
        Idempotent idempotent = method.getAnnotation(Idempotent.class);
        if (!StringUtils.hasText(idempotent.keyPrefix())) {
            throw new RuntimeException("Idempotent keyPrefix 不能为空！");
        }
        final String lockKey = getLockKey(joinPoint);
        RLock lock = redissonClient.getLock(lockKey);
        boolean isLocked = false;
        try {
            isLocked = lock.tryLock();
            if (!isLocked) {
                throw new RException(idempotent.message());
            }
            lock.lock(idempotent.timeout(), idempotent.timeUnit());
            try {
                return joinPoint.proceed();
            } catch (RException re) {
                throw re;
            } catch (Throwable throwable) {
                throw new RuntimeException(throwable);
            }
        } finally {
          if (isLocked && lock.isHeldByCurrentThread()) {
              lock.unlock();
          }
        }
    }

    public static String getLockKey(ProceedingJoinPoint joinPoint) {
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        Method method = methodSignature.getMethod();
        Idempotent idempotent = method.getAnnotation(Idempotent.class);
        final Object[] args = joinPoint.getArgs();
        final Parameter[] parameters = method.getParameters();
        StringBuilder sb = new StringBuilder();
        sb.append(idempotent.keyPrefix());
        if (idempotent.keySignature()) {
            sb.append(idempotent.delimiter())
                    .append(methodSignature.toShortString());
        }
        boolean hasParamKey = false;
        for (int i = 0; i < parameters.length; i++) {
            IdempotentParamKey paramKey = parameters[i].getAnnotation(IdempotentParamKey.class);
            if (paramKey != null && !paramKey.use()) {
                continue;
            }
            boolean hashcode = paramKey == null || paramKey.hashcode();
            sb.append(idempotent.delimiter());
            Object arg = args[i];
            if (hashcode) {
                if (arg != null) {
                    sb.append(arg.hashCode());
                }
            } else {
                sb.append(arg);
            }
            hasParamKey = true;
        }
        if (!hasParamKey && args.length > 0) {
            // 没有配置过参数 Key，将第一个参数变成参数 Key
            Object arg = args[0];
            if (arg != null) {
                sb.append(idempotent.delimiter());
                sb.append(arg.hashCode());
            }
        }
        return sb.toString();
    }
}
